Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

deku

Package Overview
Dependencies
Maintainers
2
Versions
87
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

deku

Create view components using a virtual DOM

  • 0.4.12
  • Source
  • npm
  • Socket score

Version published
Weekly downloads
538
increased by16.96%
Maintainers
2
Weekly downloads
 
Created
Source

Deku

version Circle CI js-standard-style Slack

A library for creating UI components using virtual DOM as an alternative to React. Deku has a smaller footprint (~8kb), a functional API, and doesn't support legacy browsers.

To install:

npm install deku

You can also use Duo, Bower or download the files manually.

Components are just plain objects that have a render function instead of using classes or constructors:

// button.js

let Button = {
  render (component) {
    let {props, state} = component
    return <button class="Button" type={props.kind}>{props.children}</button>
  },
  afterUpdate (component, prevProps, prevState, updateState) {
    let {props, state} = component
    if (!state.clicked) {
      updateState({ clicked: true })
    }
  }
}

export {Button}

Components are then rendered by mounting it in a tree:

import {Button} from './button'
import {tree,render,renderString} from 'deku'

let app = tree(
  <Button kind="submit">Hello World!</Button>
)

render(app, document.body)

Trees can be rendered on the server too:

let str = renderString(app)

Docs

Components

Each element of your UI can be broken into encapsulated components. These components manage the state for the UI element and tell it how to render. In Deku components are just plain objects:

function render (component) {
  let {props, state} = component
  return <button class="Button">{props.children}</button>
}

export default {render}

There is no concept of classes or use of this. We can import this component using the standard module syntax:

import Button from './button'

Read more about components

Rendering Components

To render this to the DOM we need to create a tree. This is one of the other main differences between React and Deku. The tree will manage loading data, communicating between components and allows us to use plugins on the entire application.

import {element,tree} from 'deku'
var app = tree(<Button>Hello World</Button>)

The app object has only a couple of methods:

  • .set(name, value) to set environment data
  • .option(name, value) to set rendering options
  • .mount(vnode) to change the virtual element currently mounted
  • .use(fn) to use a plugin. The function is called with the app object.

You can render this tree anyway you like, you just need a renderer for it. Let's use the DOM renderer for the client:

import Button from './button'
import {element,tree,render} from 'deku'

var app = tree(<Button>Hello World</Button>)
render(app, document.body)

And render the same thing to a string on the server:

import koa from 'koa'
import {element,tree,renderString} from 'deku'

let app = koa()

app.use(function *() {
  this.body = renderString(tree(<Button>Hello World</Button>))
})

And you can isolate functionality by using plugins. These plugins can call set to add data to the tree that your components can then access through their props:

app.use(analytics)
app.use(router)
app.use(api(writeKey))

Composition

You can compose components easily by just requiring them and using them in the render function:

import Button from './button'
import Sheet from './sheet'

function render (component) {
  return (
    <div class="MyCoolApp">
      <Sheet>
        <Button style="danger">One</Button>
        <Button style="happy">Two</Button>
      </Sheet>
    </div>
  )
}

Event handlers

Deku doesn't use any form of synthetic events because we can just capture every event in newer browsers. There are special attributes you can add to virtual elements that act as hooks to add event listeners:

function render (component) {
  let {props, state} = component
  return <button onClick={clicked}>{props.children}</button>
}

function clicked (event, component, updateState) {
  alert('You clicked it')
}

You can view all event handlers in code.

You can access the event, the component and update the state in event handlers:

function clicked (event, component, updateState) {
  let {props,state} = component
}

To access the element you'll usually want to event.target. This is the element the event was triggered on. We also set event.delegateTarget that will always be set to the element that owns the handler if it was a deeper element that triggered the event.

Lifecycle hooks

Just like the render function, component lifecycle hooks are just plain functions:

function afterUpdate (component, prevProps, prevState, updateState) {
  let {props, state} = component
  if (!state.clicked) {
    updateState({ clicked: true })
  }
}

We have hooks for beforeMount, afterMount, beforeUpdate, afterUpdate, beforeUnmount and two new hooks - beforeRender and afterRender that are called on every pass, unlike the update hooks. We've found that these extra hooks have allowed us to write cleaner code and worry less about the state of the component.

Learn more about the lifecycle hooks

Prop Validation

Prop validation isn't handle by Deku, but because we're dealing with pure functions it's easy enough to just compose it yourself using some other validation library:

import schema from 'validate'

let {assert} = schema({
  name: {
    type: 'string',
    required: true,
    message: 'name is required'
  }
})

let render = function({ props, state }) {
  assert(props)
  return <div></div>
}

External data and communication

It's often useful for components to have access to data from the outside world without needing to pass it down through components. You can set data on your tree and components can ask for it using propTypes.

First we set some data on the app:

app.set('currentUser', {
  id: 12435,
  username: 'anthonyshort',
  name: 'Anthony Short'
})

Then in our components we define the prop using the source option:

let propTypes = {
  user: {
    source: 'currentUser'
  }
}

Whenever we change that value in our app all components that depend on it will be re-rendered with the latest value. We use this pattern to pass functions down to interact with the API:

app.set('updateProject', function (project, updates) {
  api.projects.update(project, updates)
})

Which the component can access using props.updateProject. Although it may not be as complex or optimized as Relay and GraphQL it's extremely simple and covers most use cases we've run into so far. We even use this pattern to treat the router as a data source:

router.on('/projects/:id', function (params) {
  let project = api.projects.get(params.id)
  app.set('currentRoute', {
    name: 'view project',
    project: project
  })
})

This means we don't need to use some complex routing library. We just treat it like all other types of external data and components will render as needed.

Keys

Sometimes when you're rendering a list of items you want them to be moved instead of trashed during the diff. Deku supports this using the key attribute on components:

function render (component) {
  let {items} = component.props
  let projects = items.map(function (project) {
    return <ProjectItem key={project.id} project={project} />
  })
  return <div class="ProjectsList">{projects}</div>
}

At the moment we only support the key attribute on components for simplicity. Things become slightly more hairy when moving elements around within components. So far we haven't ran into a case where this has been a major problem.

Experimental: ES7 async functions

The purpose of most lifecycle hooks is usually to update the state, either by inspecting the DOM or fetching some external resources. We can simplify the concept of the lifecycle hooks by making the pure using ES7 async functions.

async function afterMount ({ props }, el) {
  var items = await request(props.url)
  var projects = await Projects.getAll()

  // Return an object to update state
  return { 
    items: items,
    projects: projects,
    loaded: true
  }
}

Instead of using the updateState function we can just return an object that will be merged in with the current state. We can do this because the lifecycle hooks are able to return a promise that resolves into a state change. All you need to do is return a promise and resolve it with an object.

We could do this with standard promises too:

function afterMount ({ props }, el) {
  return request(props.url)
    .then(Projects.getAll)
    .then(function(items, projects){
      return {
        items: items,
        projects: projects,
        loaded: true
      }
    })
}

innerHTML

You can set a string of html to be set as innerHTML using the innerHTML attribute on your virtual elements:

<div innerHTML="<span>hi</span>" />

Deku doesn't do any sanitizing of the HTML string so you'll want to do that yourself to prevent XSS attacks.

Tests

Sauce Test Status

Developing

Deku is built with Browserify. You can run the tests in a browser by running make test.

License

MIT. See LICENSE.md

FAQs

Package last updated on 28 Jul 2015

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc